<?php

namespace Swork\Pool\Redis;

use Swork\Exception\RedisException;
use Swork\Pool\AbstractConnection;
use Swork\Service;

/**
 * Interface ConnectInterface
 * @package Swoft\Pool
 */
class RedisConnection extends AbstractConnection
{
    /**
     * 当前连接对象
     * @var \Redis
     */
    private $connection;

    /**
     * Create connection
     * @return void
     * @throws
     */
    public function create()
    {
        //获取参数
        $opts = $this->config->getUri();
        $host = $opts['host'];
        $nodes = explode(',', $host);
        $password = $opts['password'] ?? '';
        $masterName = $opts['masterName'] ?? '';

        //集群模式
        if ($masterName == '' && count($nodes) > 1)
        {
            $this->connection = new \RedisCluster(null, $nodes);
            if ($password != '')
            {
                $this->connection->auth($password);
            }
            return;
        }

        //哨兵模式
        if ($masterName != '')
        {
            $host = null;
            $port = null;
            foreach ($nodes as $node)
            {
                try
                {
                    [$nodeHost, $nodePort] = explode(':', $node);
                    $sentinel = new \RedisSentinel($nodeHost, $nodePort, 2.5, null, 0, 3);
                    $masterInfo = $sentinel->getMasterAddrByName($masterName);
                    if (!empty($masterInfo))
                    {
                        $host = $masterInfo[0];
                        $port = $masterInfo[1];
                        break;
                    }
                }
                catch (\Throwable $throwable)
                {
                    Service::$logger->error($throwable->getMessage(), [__CLASS__]);
                }
            }
            if (is_null($host) || is_null($port))
            {
                throw new RedisException('RedisSentinel connect failed');
            }
        }
        else
        {
            //单机模式拆分配置
            [$host, $port] = explode(':', $host);
        }

        //单机模式 or 哨兵模式连接RedisClint
        $this->connection = new \Redis();
        $connected = $this->connection->connect($host, $port);
        if ($password != '')
        {
            $this->connection->auth($password);
        }
        if (!$connected)
        {
            throw new RedisException('RedisClint connect failed');
        }
    }

    /**
     * 重新连接
     * @throws
     */
    public function reconnect()
    {
        $this->create();
    }

    /**
     * 检查是否连接中
     * @return bool
     */
    public function check(): bool
    {
        return $this->connection->info();
    }

    /**
     * 魔术调用方法
     * @param string $name 指令名
     * @param string $arguments 指令参数
     * @return mixed
     */
    public function __call($name, $arguments)
    {
        $rel = false;
        $prefix = $this->config->getPrefix() ?? '';
        $key = $prefix . $arguments[0];
        $params = $arguments[1];

        //重试连接Redis（最多三次）
        for ($tryNum = 0; $tryNum < 3; $tryNum += 1)
        {
            try
            {
                //分不同参数个数调用
                switch (count($params))
                {
                    case 0:
                        $rel = $this->connection->$name($key);
                        break;
                    case 1:
                        $rel = $this->connection->$name($key, $params[0]);
                        break;
                    case 2:
                        $rel = $this->connection->$name($key, $params[0], $params[1]);
                        break;
                    case 3:
                        $rel = $this->connection->$name($key, $params[0], $params[1], $params[3]);
                        break;
                }

                //正常完成
                break;
            }
            catch (\Throwable $exception)
            {
                $this->reconnect();
            }
        }

        //返回
        return $rel;
    }
}
